using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Reflection;
using Server;
using Server.Items;
using Server.Mobiles;
using Server.Network;
using Server.Gumps;
using Server.Targeting;
using Server.Multis;
using Server.ContextMenus;
using Server.Regions;


namespace Server.Mobiles
{
	public interface IHardsellable
	{
		Container HoldBox { get; set; }
		Container ShowBox { get; set; }
		VendorManager Manager { get; set; }
		Hashtable ShopItems { get; set; }
		Type Currency { get; set; }
		CurrencyInfo CInfo { get; set; }
		int HoldGold { get; set; }
		int CommRate { get; set; }
		bool IsComm { get; set; }
		bool AllowBid { get; set; }
		bool BondOnly { get; set; }
		Mobile Owner { get; set; }
		List<BidByItem> BidItems { get; set; }

		bool IsOwner( Mobile m );
		void AddBidItems( Mobile m, BidByItem b );
	}

	public class BidByItem
	{
		public object m_itemobj;
		public TimeSpan m_duration;
		public DateTime m_start;

		public List<BidItem> m_byprice = new List<BidItem>();

		public int m_lowest;

		public bool IsExpired
		{
			get
			{
				return DateTime.Compare( DateTime.Now, m_start + m_duration ) > 0;
			}
		}

		public DateTime Expire
		{
			get{ return m_start + m_duration; }
		}

		public BidByItem( object itemobj, TimeSpan duration )
		{
			m_itemobj = itemobj;
			m_start = DateTime.Now;
			m_duration = duration;

			m_lowest = 0;
		}

		public bool Add( Mobile bider, int bid )
		{
			if ( !( bid > m_lowest ) || IsExpired )
			{
//				bider.SendMessage( "bid too low, or expired {0} - {1} : {2}.", bid, m_lowest, IsExpired.ToString() );		// debug code
				return false;
			}

			if ( m_byprice != null && m_byprice.Count > 0 )
			{
				foreach ( BidItem bbp in m_byprice )
				{
					if ( bbp.m_bider == bider )
					{
						bbp.m_bid = bid;
						return true;
					}
				}
			}

			m_byprice.Add( new BidItem( bider, bid ) );
			m_byprice.Sort( new BidItemComparer() );
			TruncateSorted();

			return true;
		}

		// TODO: max count is hard coded at the moment
		private void TruncateSorted()
		{
			if ( m_byprice != null && m_byprice.Count > 10 )
			{
				for ( int i = 10; i < m_byprice.Count; i++ )
					m_byprice.RemoveAt( i-- );
			}

			int index = m_byprice.Count - 1;
			m_lowest = m_byprice[index].m_bid;
		}
	}

	public class BidItem
	{
		public Mobile m_bider;
		public int m_bid;

		public BidItem( Mobile bider, int bid )
		{
			m_bider = bider;
			m_bid = bid;
		}
	}

	public class BidItemComparer : IComparer<BidItem>
	{
		public int Compare( BidItem x, BidItem y )
		{
			return Compare( (int)((BidItem)x).m_bid, (int)((BidItem)y).m_bid );
		}

		public int Compare( int x, int y )
		{
			return ( y - x );
		}
	}

	public class BidRecord
	{
		public Serial m_serial;		// item serial, item may or may exist in vendor
		public string m_name;			// item name
		public Mobile m_vendor;
		public int m_bid;
		public DateTime m_expire;
		public string m_comment;

		public BidRecord( Serial serial, string name, Mobile vendor, int bid, DateTime expire, string comment )
		{
			m_serial = serial;
			m_name = name;
			m_vendor = vendor;
			m_bid = bid;
			m_expire = expire;
			m_comment = comment;
		}
	}

	public class BidRecordComparer : IComparer<BidRecord>
	{
		public int Compare( BidRecord x, BidRecord y )
		{
			return Compare( (DateTime)((BidRecord)x).m_expire, (DateTime)((BidRecord)y).m_expire );
		}

		public int Compare( DateTime x, DateTime y )
		{
			if ( DateTime.Compare( x, y ) < 0 )
				return -1;
			else return 1;
		}
	}

	public class TradeMobile
	{
		public Mobile m_owner;
		public Mobile m_vendor;
		public BaseCreature m_pet;

		public TradeMobile( Mobile from, Mobile vendor, BaseCreature pet )
		{
			m_owner = from;
			m_vendor = vendor;
			m_pet = pet;
		}
	}

	public class VendorPetInfo
	{
		private Mobile m_From;
		private BaseCreature m_Pet;

		private List<PetProp> m_PetPropInfo;

		private static string[] m_PetProps = new string[22]
		{
			"Title",
			"Female",
			"Level",
			"MaxLevel",
			"Exp",
			"NextLevel",
			"DamageMax",
			"DamageMin",
			"VirtualArmor",
			"ArmorRating",
			"RoarAttack",
			"FireBreathAttack",
			"PetPoisonAttack",
			"Generation",
			"Stage",
			"Ep",
			"ControlSlots",
			"MinTameSkill",
			"Tamable",
			"MatingDelay",
			"Fame",
			"Karma",
		};

		public static string[] PetProps
		{
			get{ return m_PetProps; }
		}

		public List<PetProp> PetPropInfo
		{
			get{ return m_PetPropInfo; }
		}

		public VendorPetInfo( Mobile from, BaseCreature pet )
		{
			m_From = from;
			m_Pet = pet;

			LoadPetProps();
		}

		public void LoadPetProps()
		{
			m_PetPropInfo = new List<PetProp>();
			ArrayList list = CommonTools.BuildPropsList( m_From, (object)m_Pet );

			if ( list == null || !( list.Count > 0 ) )
				return;

			for ( int i = 0; i < list.Count; i++ )
			{
				object o = list[i];

				if ( o is PropertyInfo )
				{
					PropertyInfo prop = (PropertyInfo)o;
					string sval = CommonTools.ValueToString( (object)m_Pet, prop );

					for ( int j = 0; j < PetProps.Length; j++ )
					{
						if ( prop.Name == PetProps[j] )
						{
							m_PetPropInfo.Add( new PetProp( prop.Name, sval ) );
							m_PetPropInfo.Sort( new PetPropComparer() );
						}
					}
				}
			}

			for ( int i = 0; i < m_Pet.Skills.Length; ++i )
			{
				if ( m_Pet.Skills[i].Value > 0 )
					m_PetPropInfo.Add( new PetProp( ((SkillName)i).ToString(), m_Pet.Skills[i].Value.ToString() ) );
			}
		}

	}

	public class PetProp
	{
		public string m_name;
		public string m_value;

		public PetProp( string name, string value )
		{
			m_name = name;
			m_value = value;
		}
	}

	public class PetPropComparer : IComparer<PetProp>
	{
		public int Compare( PetProp x, PetProp y )
		{
			int px = 0;
			int py = 0;

			for ( int i = 0; i < VendorPetInfo.PetProps.Length; i++ )
			{
				if ( x.m_name == VendorPetInfo.PetProps[i] )
					px = i;

				if ( y.m_name == VendorPetInfo.PetProps[i] )
					py = i;
			}
			return Compare( px, py );
		}

		public int Compare( int x, int y )
		{
			return ( x - y );
		}
	}


	public class TradeItem
	{
		private object m_ItemObj;
		private int m_Price;
		private string m_Description;

		public object ItemObj{ get{ return m_ItemObj; } }

		public int Price
		{
			get{ return m_Price; }
			set
			{
				if ( value < 0 )
					value = -1;

				m_Price = value;

				((Item)ItemObj).InvalidateProperties();
			}
		}

		public string Description
		{
			get{ return m_Description; }
			set
			{
				if ( value != null )
					m_Description = value;
				else
					m_Description = "";

				((Item)ItemObj).InvalidateProperties();
			}
		}

		public bool IsForSale{ get{ return Price >= 0; } }
		public bool IsForFree{ get{ return Price == 0; } }
		public bool IsForDemo{ get{ return Price < 0; } }

		public TradeItem( object item, int price, string description )
		{
			m_ItemObj = item;
			m_Price = price;

			if ( description != null )
				m_Description = description;
			else
				m_Description = "";
		}
	}

	public class CurrencyInfo
	{
		public int m_itemID;
		public int m_hue;
		public bool m_dupeicon;

		public CurrencyInfo( int itemID, int hue, bool dupeicon )
		{
			m_itemID = itemID;
			m_hue = hue;
			m_dupeicon = dupeicon;
		}
	}


	public class VendorManager : BaseVendor
	{
		private ArrayList m_SBInfos = new ArrayList();
		protected override ArrayList SBInfos{ get { return m_SBInfos; } }
		private static Regex m_PluralRegEx 
			= new Regex( @"([^%]+)%([^%/ ]+)(/([^% ]+))*%*([^%]*)", RegexOptions.Compiled | RegexOptions.Singleline );


		public static int[] MallTiles = new int[19]
		{
			0x3F08,
			0x3F09,
			0x3F0A,
			0x3F0D,
			0x3F11,
			0x3F17,
			0x3F18,
			0x0AC7,
			0x0AD1,
			0x0ADA,
			0x0AEB,
			0x0AEC,
			0x0AED,
			0x0AFA,
			0x17DC,
			0x17E5,
			0x17EE,
			0x2E1D,
			0x2EBE,
		};

		private VendorRegion m_Region;
		private Rectangle3D[] m_VendorArea;
		private Point3D m_AreaHome;
		private int m_AreaRange;
		private int m_CommRate;
		private int m_BidPeriod;
		private int m_ExtraSlot;
		private int m_OwnerSerial;
		private bool m_AllowHousing;
		private bool m_RestrictMark;
		private bool m_RestrictTeleport;
		private bool m_RestrictRecall;

		private bool m_AllowBid;
		private bool m_BondOnly;
		private string m_StringName;
		private Direction m_Dir;

		private int m_TileID;
		private int m_TileHue;

		private DateTime m_ShowTime;
		private double m_ShowSpan;
		private TimeSpan m_ShowDelay;

		private List<Mobile> m_Workers;


		public Rectangle3D[] VendorArea
		{
			get{ return m_VendorArea; }
			set{ m_VendorArea = value; }
		}

		public VendorRegion VRegion
		{
			get{ return m_Region; }
		}

		public Point3D AreaHome
		{
			get{ return m_AreaHome; }
			set{ m_AreaHome = value; InvalidateProperties(); }
		}

		public int AreaRange
		{
			get{ return m_AreaRange; }
			set{ m_AreaRange = value; InvalidateProperties(); }
		}

		[CommandProperty( AccessLevel.GameMaster)]
		public int CommRate
		{
			get{ return m_CommRate; }
			set{ m_CommRate = value; InvalidateProperties(); }
		}

		[CommandProperty( AccessLevel.GameMaster)]
		public int BidPeriod
		{
			get{ return m_BidPeriod; }
			set{ m_BidPeriod = value; InvalidateProperties(); }
		}

		[CommandProperty( AccessLevel.GameMaster)]
		public int ExtraSlot
		{
			get{ return m_ExtraSlot; }
			set{ m_ExtraSlot = value; InvalidateProperties(); }
		}

		[CommandProperty( AccessLevel.GameMaster)]
		public bool AllowHousing
		{
			get{ return m_AllowHousing; }
			set{ m_AllowHousing = value; InvalidateProperties(); }
		}

		[CommandProperty( AccessLevel.GameMaster)]
		public bool RestrictMark
		{
			get{ return m_RestrictMark; }
			set{ m_RestrictMark = value; InvalidateProperties(); }
		}

		[CommandProperty( AccessLevel.GameMaster)]
		public bool RestrictTeleport
		{
			get{ return m_RestrictTeleport; }
			set{ m_RestrictTeleport = value; InvalidateProperties(); }
		}

		[CommandProperty( AccessLevel.GameMaster)]
		public bool RestrictRecall
		{
			get{ return m_RestrictRecall; }
			set{ m_RestrictRecall = value; InvalidateProperties(); }
		}

		[CommandProperty( AccessLevel.GameMaster)]
		public bool AllowBid
		{
			get{ return m_AllowBid; }
			set{ m_AllowBid = value; InvalidateProperties(); }
		}

		[CommandProperty( AccessLevel.GameMaster)]
		public bool BondOnly
		{
			get{ return m_BondOnly; }
			set{ m_BondOnly = value; InvalidateProperties(); }
		}

		public string StringName
		{
			get{ return m_StringName; }
			set{ m_StringName = value; InvalidateProperties(); }
		}

		[CommandProperty( AccessLevel.GameMaster)]
		public Direction Dir
		{
			get{ return m_Dir; }
			set{ m_Dir = value; InvalidateProperties(); }
		}

		public int TileID
		{
			get{ return m_TileID; }
			set{ m_TileID = value; InvalidateProperties(); }
		}

		[CommandProperty( AccessLevel.GameMaster)]
		public int TileHue
		{
			get{ return m_TileHue; }
			set{ m_TileHue = value; InvalidateProperties(); }
		}

		public List<Mobile> Workers
		{
			get{ return m_Workers; }
			set{ m_Workers = value; InvalidateProperties(); }
		}

		public DateTime ShowTime
		{
			get{ return m_ShowTime; }
			set{ m_ShowTime = value; InvalidateProperties(); }
		}

		[CommandProperty( AccessLevel.GameMaster)]
		public double ShowSpan
		{
			get{ return m_ShowSpan; }
			set{ m_ShowSpan = value; InvalidateProperties(); }
		}

		public TimeSpan ShowDelay
		{
			get{ return m_ShowDelay; }
			set{ m_ShowDelay = value; InvalidateProperties(); }
		}


		[Constructable]
		public VendorManager( Mobile owner ) : this( owner, "the Manager" )
		{
		}

		[Constructable]
		public VendorManager( Mobile owner, string title ) : base( title )
		{
			Hidden = true;
			Blessed = true;
			CantWalk = true;
			Frozen = true;
			Direction = Direction.East;

			m_Dir = Direction.East;
			m_Workers = new List<Mobile>();

			m_AreaHome = Point3D.Zero;
			m_AreaRange = 0;
			m_CommRate = 5;
			m_BidPeriod = 12;
			m_AllowBid = true;
			m_AllowHousing = false;
			m_RestrictMark = false;
			m_BondOnly = true;
			m_ExtraSlot = 5;
			m_StringName = this.Name.ToString() + ( (int)this.Serial ).ToString();

			// to avoid an empty default constructor causing any 
			// timing problem when adding a vendor manager
			int m_OwnerSerial = (int)owner.Serial;

			m_ShowSpan = 12.0;
			m_ShowDelay = TimeSpan.FromMinutes( 2.0 );
			m_ShowTime = DateTime.Now + m_ShowDelay;

			Item tile = new CommVendorTile();
			m_TileID = tile.ItemID;
			m_TileHue = tile.Hue;
		}

		public VendorManager( Serial serial ) : base( serial )
		{
		}

		public override void InitSBInfo()
		{
			m_SBInfos.Add( new SBVendorManager( (int)this.Serial ) );
		}

//		private int accum = 0;		// debug code

		public override void OnThink()
		{
			Direction = m_Dir;

			if ( m_Workers != null && m_Workers.Count > 0 )
			{
				for ( int i = 0; i < m_Workers.Count; i++ )
				{
					Mobile worker = m_Workers[i] as Mobile;

					if ( worker == null || worker.Deleted )
						m_Workers.RemoveAt( i-- );
					else if ( worker is CommissionedVendor )
					{
						CommissionedVendor cv = worker as CommissionedVendor;

//						if ( accum % 10 == 0 && cv.CommTimer != null && cv.CommTimer.Running )		// debug code
//							Console.WriteLine( "Clock is running! Next: {0}.", cv.CommTimer.Next.ToString( "dd/MMM/yyyy HH:mm" ) );
//						else if ( accum % 10 == 0 )
//							Console.WriteLine( "Clock is null or not running!" );

						if ( cv.IsComm )
						{
							if ( cv.DailyPayTimer != null && cv.DailyPayTimer.Running )
							{
								cv.DailyPayTimer.Stop();
								cv.DailyPayTimer = new Timer( TimeSpan.FromDays( 9999.0 ) );
							}

							if ( cv.CommTimer == null || !cv.CommTimer.Running )
								cv.ConstructCommTimer();
						}
					}

				}
			}
//			accum++;	// debug code

			base.OnThink();
		}

		public override void OnDoubleClick( Mobile from )
		{
			if( CanPaperdollBeOpenedBy( from ) )
				DisplayPaperdollTo( from );
		}

		public void BeginMarkArea( Mobile m, VendorManager vm )
		{
			BoundingBoxPicker.Begin( m, new BoundingBoxCallback( MarkArea_Callback ), vm );
		}

		private void MarkArea_Callback( Mobile m, Map map, Point3D start, Point3D end, object state )
		{
			string content = " The following area will be added,<br><br> " 
				+ String.Format( "( {0}, {1}, {2} )", start.X, start.Y, start.Z )
				+ String.Format( " - ( {0}, {1}, {2} )", end.X, end.Y, end.Z )
				+ "<br><br> Do you wish to proceed?";

			m.SendGump( new WarningGump( 1060637, 30720, content, 0xFFC000, 420, 280, new WarningGumpCallback( ConfirmMark_Callback ), new StateInfo( map, start, end, (VendorManager)state ) ) );
		}

		public void ConfirmMark_Callback( Mobile m, bool ok, object state )
		{
			if ( !ok )
				return;

			StateInfo si = (StateInfo)state;

			DoMark( m, si.m_Map, si.m_Start, si.m_End, si.m_Manager );
		}

		private void DoMark( Mobile m, Map map, Point3D start, Point3D end, VendorManager vm )
		{
			if ( vm == null || vm.Deleted )
			{
				m.SendMessage( "Manager doesn't exist." );
				return;
			}

			if ( map != vm.Map )
				return;

			int maxrange = 0;
			int range = 0;
			Point3D phome = Point3D.Zero;

			List<Rectangle3D> areas = new List<Rectangle3D>();

			if ( m_VendorArea != null && m_VendorArea.Length > 0 )
			{
				foreach ( Rectangle3D rect in vm.VendorArea )
				{
					areas.Add( rect );
					range = (int)( Math.Sqrt( rect.Width * rect.Width + rect.Height * rect.Height ) / 2 );
	
					if ( range > maxrange )
					{
						maxrange = range;
	
						int x = (int)( rect.Start.X + rect.Width / 2 );
						int y = (int)( rect.Start.Y + rect.Height / 2 );
						phome = new Point3D( x, y, map.GetAverageZ( x, y ) );
					}
				}
			}

      if ( start.Z <= end.Z )
      {
      	start.Z = Server.Region.MinZ;
      	end.Z = Server.Region.MaxZ;
      }
      else
      {
      	start.Z = Server.Region.MaxZ;
      	end.Z = Server.Region.MinZ;
      }

      Rectangle3D area = new Rectangle3D( start, end );
      areas.Add( area );

			range = (int)( Math.Sqrt( area.Width * area.Width + area.Height * area.Height ) / 2 );
			if ( range > maxrange )
			{
				maxrange = range;

				int x = (int)( area.Start.X + area.Width / 2 );
				int y = (int)( area.Start.Y + area.Height / 2 );
				phome = new Point3D( x, y, Map.GetAverageZ( x, y ) );
			}

			m_AreaHome = phome;
			m_AreaRange = maxrange;

      m_VendorArea = areas.ToArray();
      UpdateMallRegion();
      Hidden = false;

			string content = string.Format( "( {0}, {1}, {2} )", start.X, start.Y, start.Z )
				+ String.Format( " - ( {0}, {1}, {2} )", end.X, end.Y, end.Z );

//			m.SendMessage( "Area: {0}, HomeRange: {1}.", content, range );	// debug code
			m.SendMessage( "Region set: {0}.", m_Region.Name.ToString() );
    }

		public void UpdateMallRegion()
		{
			if ( m_Region != null )
				m_Region.Unregister();

			if ( m_VendorArea != null && m_VendorArea.Length > 0 )
			{
				m_Region = new VendorRegion( this );
				m_Region.Register();
			}
			else
				m_Region = null;
		}

		public override void OnDelete()
		{
			m_VendorArea = null;
			UpdateMallRegion();

			base.OnDelete();
		}

		public bool FindMobileInMall( Mobile m, VendorManager vm )
		{
			if ( m.Map != vm.Map || m_VendorArea == null || !( m_VendorArea.Length > 0 ) )
				return false;

			Point3D loc = m.Location;
			bool found = false;

			foreach ( Rectangle3D rect in m_VendorArea )
			{
				for ( int x = rect.Start.X; x < rect.End.X; x++ )
				{
					for ( int y = rect.Start.Y; y < rect.End.Y; y++ )
					{
						if ( x == loc.X && y == loc.Y )
							found = true;
					}
				}
			}

			return found;
		}

		public static void ToSpawnCommVendor( Mobile m, VendorManager vm, Type type )
		{
			if ( vm == null || vm.Deleted )
				return;

			if ( !vm.FindMobileInMall( m, vm ) )
			{
				m.SendMessage( "You must be inside the mall to do that." );
				return;
			}

			if ( vm.AreaHome == Point3D.Zero || !( vm.AreaRange > 0 ) )
			{
				m.SendMessage( "You need to clear and remark the mall areas!" );
				return;
			}

			Timer.DelayCall( TimeSpan.Zero, new TimerStateCallback( SpawnMobileCallback ), new object[] { m, vm, type } );

		}

		private static void SpawnMobileCallback( object state )
		{
			object[] aState = (object[]) state;

			Mobile m = (Mobile) aState[0];
			VendorManager vm = (VendorManager) aState[1];
			Type type = (Type) aState[2];

			Mobile vendor = m;

			// Restockable vendor is inherited from Wandering vendor
			if ( type == typeof( PetSeller ) )
				vendor = new PetSeller( m, vm );
			if ( type == typeof( RestockableVendor ) )
				vendor = new RestockableVendor( m, vm );
			else if ( type == typeof( WanderingTrader ) )
				vendor = new WanderingTrader( m, vm );

			if ( vendor == m )
			{
				m.SendMessage( "Vendor not created." );
				return;
			}

			IHardsellable hs = vendor as IHardsellable;

			hs.Manager = vm;
			hs.CommRate = vm.CommRate;
			hs.Owner = vm as Mobile;

			((BaseCreature)vendor).Home = vm.AreaHome;
			((BaseCreature)vendor).RangeHome = vm.AreaRange;

			vendor.Direction = m.Direction & Direction.Mask;

			vm.Workers.Add( (Mobile)vendor );
			vendor.MoveToWorld( m.Location, m.Map );
		}

		public static void AllGoHome( Mobile from, VendorManager vm )
		{
			Map map = vm.Map;
			Point3D loc = vm.AreaHome;

			if ( loc == Point3D.Zero )
				loc = vm.Location;

			for ( int i = 0; i < vm.Workers.Count; i++ )
			{
				Mobile vendor = vm.Workers[i];

				if ( !( vendor is CommissionedVendor ) )
				{
					((BaseCreature)vendor).Home = vm.AreaHome;
					((BaseCreature)vendor).RangeHome = vm.AreaRange;

					BaseCreature.TeleportPets( vendor, loc, map );
					vendor.MoveToWorld( loc, map );
				}
			}
		}

		public static void ToSetupTile( Mobile from, VendorManager vm )
		{
			if ( vm == null || vm.Deleted )
				return;

			from.CloseGump( typeof( TileSetupGump ) );
			from.SendGump( new TileSetupGump( vm ) );
		}

		public static void ToConvertTiles( Mobile from, Mobile manager )
		{
			VendorManager vm = manager as VendorManager;

			if ( vm == null || vm.Deleted || vm.VendorArea == null || !( vm.VendorArea.Length > 0 ) )
				return;

			Map map = vm.Map;

			IPooledEnumerable eable = null;

			foreach ( Rectangle3D rect in vm.VendorArea )
			{
				Rectangle2D area = new Rectangle2D( new Point2D( rect.Start.X, rect.Start.Y ), new Point2D( rect.End.X, rect.End.Y ) );

				eable = map.GetItemsInBounds( area );

				foreach ( Item item in eable )
				{
					if ( item is CommVendorTile )
					{
						Item tile = new CommVendorTile( vm.TileID, vm.TileHue );

						Timer.DelayCall( TimeSpan.Zero, new TimerStateCallback( SpawnTile_Callback ), new object[] { item, tile } );
					}
				}
			}

			eable.Free();
		}

		private static void SpawnTile_Callback( object state )
		{
			object[] aState = (object[]) state;

			Item item = (Item) aState[0];
			Item tile = (Item) aState[1];

			Map map = item.Map;
			Point3D loc = item.Location;

			item.Internalize();
			item.Delete();

			tile.MoveToWorld( loc, map );
		}

		public static void ManagerComeHere( Mobile m, VendorManager vm )
		{
			if ( !vm.FindMobileInMall( m, vm ) )
			{
				m.SendMessage( "You must be inside the mall to do that." );
				return;
			}

			vm.Direction = m.Direction & Direction.Mask;
			vm.MoveToWorld( m.Location, m.Map );

			vm.Dir = vm.Direction;
		}

		public static void RecallToVendor( Mobile from, Mobile vendor )
		{
			if ( vendor == null || vendor.Deleted )
			{
				from.SendMessage( "Vendor no long exists." );
				return;
			}

			IHardsellable hs = vendor as IHardsellable;

			string mname = "[none]";
			if ( hs.Manager != null && !hs.Manager.Deleted )
				mname = hs.Manager.Name;

			string content = " Recall to the following vendor,<br><br>"
				+ string.Format( " Vendor: {0}, Manager: {1}, Map: {2}", vendor.Name, mname, vendor.Map.ToString() )
				+ "<br><br> Do you wish to proceed?";

			from.SendGump( new WarningGump( 1060637, 30720, content, 0xFFC000, 420, 280, 
				new WarningGumpCallback( ConfirmToVendor_Callback ), new object[] { from, vendor } ) );
		}

		public static void ConfirmToVendor_Callback( Mobile m, bool ok, object state )
		{
			if ( !ok )
				return;

			object[] aState = (object[]) state;

			Mobile from = (Mobile) aState[0];
			Mobile vendor = (Mobile) aState[1];

			Map map = vendor.Map;
			Point3D loc = vendor.Location;

			BaseCreature.TeleportPets( from, loc, map );

			from.Combatant = null;
			from.Warmode = false;
			from.Hidden = true;

			from.PlaySound( 0x1FC );
			from.MoveToWorld( loc, map );
			from.PlaySound( 0x1FC );
		}

		public static void ToRemoveBidRecord( Mobile from, int pos )
		{
			List<BidRecord> list = (List<BidRecord>)SaveStone.PlayerBids[from];
			BidRecord br = null;

			if ( list != null && list.Count > pos )
				br = list[pos];
			else
			{
				from.SendMessage( "Error: record does not exist." );
				return;
			}

			if ( br.m_vendor == null || br.m_vendor.Deleted )
			{
				from.SendMessage( "Vendor no longer exists." );
				return;
			}

			string content = " The following bid record will be removed,<br><br> "
				+ string.Format( " Item: {0}, Bid: {1}, Vendor: {2}", br.m_name, br.m_bid, br.m_vendor.Name )
				+ "<br><br> Do you wish to proceed?";

			from.SendGump( new WarningGump( 1060637, 30720, content, 0xFFC000, 420, 280, 
				new WarningGumpCallback( ConfirmRemoveRecord_Callback ), new object[] { from, pos } ) );
		}

		private static void ConfirmRemoveRecord_Callback( Mobile m, bool ok, object state )
		{
			if ( !ok )
				return;

			object[] aState = (object[]) state;

			Mobile from = (Mobile) aState[0];
			int pos = (int) aState[1];

			List<BidRecord> list = (List<BidRecord>)SaveStone.PlayerBids[from];

			if ( list != null && list.Count > pos )
				list.RemoveAt( pos );
		}

		public static void ToClearArea( Mobile from, VendorManager vm, int pos )
		{
			if ( vm.VendorArea == null || !( vm.VendorArea.Length > 0 ) || pos > ( vm.VendorArea.Length - 1 ) )
			{
				from.SendMessage( "Area doesn't exist or selection is out of range." );
				return;
			}

			string content = "";

			if ( pos >= 0 )
			{
				Rectangle3D rect = vm.VendorArea[pos];
	
				content = " The following area will be removed,<br><br> " 
					+ String.Format( "( {0}, {1}, {2} )", rect.Start.X, rect.Start.Y, rect.Start.Z )
					+ String.Format( " - ( {0}, {1}, {2} )", rect.End.X, rect.End.Y, rect.End.Z )
					+ "<br><br> Do you wish to proceed?";
			}
			else
			{
				content = " All the areas will be cleared.<br><br>"
					+ " Do you wish to proceed?";
			}

			from.SendGump( new WarningGump( 1060637, 30720, content, 0xFFC000, 420, 280, 
				new WarningGumpCallback( ConfirmClear_Callback ), new object[] { from, vm, pos } ) );
		}

		private static void ConfirmClear_Callback( Mobile m, bool ok, object state )
		{
			if ( !ok )
				return;

			object[] aState = (object[]) state;

			Mobile from = (Mobile) aState[0];
			VendorManager vm = (VendorManager) aState[1];
			int pos = (int) aState[2];

			try
			{
				if ( pos >= 0 )
				{
					List<Rectangle3D> areas = new List<Rectangle3D>();
	
					foreach ( Rectangle3D rect in vm.VendorArea )
						areas.Add( rect );
	
					areas.RemoveAt( pos );
	
					vm.VendorArea = areas.ToArray();
				}
				else
					vm.VendorArea = null;

				vm.UpdateMallRegion();

				from.SendMessage( "Area {0} removed!", pos + 1 );
			}
			catch
			{
				from.SendMessage( "Error: failed to remove area." );
				return;
			}
		}

		public int GetMallWorkers( Type type )
		{
			if ( m_Workers == null || !( m_Workers.Count > 0 ) )
				return 0;

			int count = 0;

			for ( int i = 0; i < m_Workers.Count; i++ )
			{
				Mobile mb = m_Workers[i];

				if ( mb.GetType() == type )
					count++;
			}

			return count;
		}

		public override void GetContextMenuEntries( Mobile from, List<ContextMenuEntry> list )
		{
			if ( from.Alive && from.AccessLevel >= AccessLevel.GameMaster && !this.Deleted )
			{
				list.Add( new ManagerSetupEntry( from, this ) );
				list.Add( new ManagerComeEntry( from, this ) );
			}

			base.GetContextMenuEntries( from, list );
		}


		// originally written by Zippy. Credits: Zippy and David of RunUO
		public static string ToGetName( Item item, bool useAmount )
		{
			StringBuilder sb = new StringBuilder();
					
			if ( useAmount && item.Amount != 1 && item.ItemID != 0x2006 )
				sb.AppendFormat( "{0} ", item.Amount );

			if ( item.Name == null || item.Name.Length <= 0 )
			{
				bool plural = item.Amount != 1;

				// bread loa%ves/f%, black pearl%s%, log%s, etc
				Match match = m_PluralRegEx.Match( item.ItemData.Name );
				if ( match.Success )
				{
					if ( match.Groups[1].Value.Length > 0 )
						sb.Append( match.Groups[1].Value );

					if ( plural )
					{
						if ( match.Groups[2].Success && match.Groups[2].Value.Length > 0 )
							sb.Append( match.Groups[2].Value );
					}
					else
					{
						if ( match.Groups[4].Success && match.Groups[4].Value.Length > 0 )
							sb.Append( match.Groups[4].Value );
					}

					if ( match.Groups[5].Value.Length > 0 )
						sb.Append( match.Groups[5].Value );
				}
				else
				{
					sb.Append( item.ItemData.Name );
					if ( plural && item.ItemID == 0x0EED )	// gold coinS dont ever get the s ( unless we put it there <-- )
						sb.Append( 's' );
				}

				if ( item.Amount == 1 && sb.Length > 0 && Char.IsLetter( sb[0] ) 
					&& ( ( item.ItemData.Flags & TileFlag.ArticleAn ) != 0 
					|| ( item.ItemData.Flags & TileFlag.ArticleA ) != 0 ) )
				{
					switch ( Char.ToUpper( sb[0] ) )
					{
						case 'A':
						case 'E':
						case 'I':
						case 'O':
						case 'U':
						case 'Y':
							sb.Insert( 0, "an " );
							break;
						default:
							sb.Insert( 0, "a " );
							break;
					}
				}
			}
			else
			{
				sb.Append( item.Name );
				if ( item.Amount != 1 && item.ItemID != 0x2006 )
					sb.Append( 's' );
			}

			return sb.ToString();
		}

		// written by David. Credits: David of RunUO
    public static string GetName( Item i )
    {
      string[] split;
      string name;

      name = i.Name;
      if ( String.IsNullOrEmpty( name ) )
        name = i.ItemData.Name;

      split = name.Split( '%' );		// doesn't work too well for bread loaves
//      name = split[0].ToLower();
			name = split[0];

      return name;
    }


		public static void ToSetupCurrency( Mobile from, Mobile vendor )
		{
			from.SendMessage( "Select the item to be the vendor's currency." );
			from.Target = new SetupCurrencyTarget( from, vendor );
		}

		public static bool CheckBidAccount( Mobile from, Mobile vendor, out BidAccountBook ba )
		{
			ba = null;
			IHardsellable hs = vendor as IHardsellable;

			if ( hs == null )
				return false;

			List<Item> list = from.Backpack.Items;
			bool found = false;

			for ( int i = 0; i < list.Count; i++ )
			{
				if ( list[i] is BidAccountBook && ((BidAccountBook)list[i]).Holder == from 
					&& !((BidAccountBook)list[i]).IsFull( from ) )
				{
					ba = (BidAccountBook)list[i];
					found = true;
				}
			}

			return found;
		}

		public bool ToUpdateBidAccount( Mobile from, BidRecord br )
		{
			List<BidRecord> list = new List<BidRecord>();

			if ( SaveStone.PlayerBids != null && SaveStone.PlayerBids.ContainsKey( from ) )
				list = (List<BidRecord>)SaveStone.PlayerBids[from];

			Serial serial = br.m_serial;

			for ( int i = 0; i < list.Count; i++ )
			{
				Serial bserial = list[i].m_serial;

				if ( bserial == serial )
					list.RemoveAt( i-- );
			}

			if ( list != null && list.Count > BidAccountBook.MaxEntry )
			{
				from.SendMessage( "Your account book is full." );
				return false;
			}

			list.Add( br );
			list.Sort( new BidRecordComparer() );

			if ( SaveStone.PlayerBids == null )
				SaveStone.PlayerBids = new Hashtable();

			SaveStone.PlayerBids[from] = list;
			return true;
		}

		public static bool ToPlaceBid( Mobile from, TradeItem ti, int bid )
		{
			IHardsellable hs = ((Item)ti.ItemObj).RootParent as IHardsellable;
			Mobile vendor = ((Item)ti.ItemObj).RootParent as Mobile;

			if ( hs == null || hs.ShopItems == null || !( hs.ShopItems.Count > 0 ) || hs.Manager == null )
				return false;

			BidAccountBook ba = new BidAccountBook();

			if ( !CheckBidAccount( from, vendor, out ba ) )
			{
				from.SendMessage( "{0} asks for a valid account book, from {1}.", vendor.Name, hs.Manager.Name );
				return false;
			}

			List<BidRecord> list = new List<BidRecord>();
			Serial serial = ((Item)ti.ItemObj).Serial;

			if ( SaveStone.PlayerBids != null && SaveStone.PlayerBids.ContainsKey( from ) )
			{
				list = (List<BidRecord>)SaveStone.PlayerBids[from];

				for ( int i = 0; i < list.Count; i++ )
				{
					if ( list[i].m_serial == serial && !( bid > list[i].m_bid ) )
					{
						from.SendMessage( "You need to raise up your bid." );
						return false;
					}
				}
			}

			BidByItem byitem = null;
			bool ok = false;

			if ( hs.BidItems != null && hs.BidItems.Count > 0 )
			{
				foreach ( BidByItem bbi in hs.BidItems )
				{
					if ( bbi.m_itemobj == ti.ItemObj && !bbi.IsExpired )
					{
						ok = true;
						byitem = bbi;
					}
				}
			}

			try
			{
				double period = (double)hs.Manager.BidPeriod;
				TimeSpan left = TimeSpan.Zero;

				if ( ok && byitem != null )
				{
					ok = byitem.Add( from, bid );
				}
				else
				{
					byitem = new BidByItem( ti.ItemObj, TimeSpan.FromDays( period ) );
					ok = byitem.Add( from, bid );

					if ( ok )
						hs.AddBidItems( from, byitem );
				}

				if ( ok )
				{
					BidRecord br = new BidRecord( serial, ti.Description, vendor, bid, byitem.Expire, "" );

					if ( hs.Manager != null )
						hs.Manager.ToUpdateBidAccount( from, br );

					if ( DateTime.Compare( byitem.Expire, DateTime.Now ) > 0 )
					{
						left = byitem.Expire - DateTime.Now;
						from.SendMessage( "Your bid is placed, check back in {0} days.", (int)left.TotalDays );

						SetLastBid( hs.Owner, byitem );
					}
					else
						from.SendMessage( "Your bid is expired." );		// this line should be unreachable
				}
				else
					from.SendMessage( "Your bid is not placed, perhaps offer a little more?" );
			}
			catch
			{
				from.SendMessage( "ERROR." );
			}

			return false;
		}

		public static bool IsMaster( Mobile from, TradeItem ti )
		{
			Item clone = (Item)ti.ItemObj;

			if ( clone is CloneMobile && ((CloneMobile)clone).Master == from )
				return true;

			return false;
		}

		public static void ToRemoveTradeItem( Mobile from, TradeItem ti )
		{
			Item item = (Item)ti.ItemObj;
			IHardsellable hs = item.RootParent as IHardsellable;
			Mobile vendor = item.RootParent as Mobile;

			if ( !(VendorManager.CanTradeWith( from, vendor ) && ( hs.IsOwner( from ) || IsMaster( from, ti ) ) ) )
			{
				from.SendMessage( "Error: Cannot interact with vendor." );
				return;
			}

			Container bank = from.FindBankNoCreate();

			if ( from.Backpack == null || bank == null )
			{
				from.SendMessage( "Error: owner backpack or bank." );
				return;
			}

			BaseCreature pet = null;
			bool ok = true;

			try
			{
				if ( item is CloneMobile )
				{
					pet = ((CloneMobile)item).ClonePet as BaseCreature;

					if ( pet == null || pet.Deleted )
					{
						from.SendMessage( "Pet is lost!" );
						ok = false;
					}

					if ( from.Map != vendor.Map || !from.InRange( vendor, 14 ) || !from.CheckAlive() )
					{
						vendor.SayTo( from, "I can't see you, sorry." );
						ok = false;
					}

					if ( ( from.Followers + pet.ControlSlots ) > from.FollowersMax )
					{
						vendor.SayTo( from, 1049607 );	// You have too many followers to control that creature.
						ok = false;
					}

					if ( ok )
					{
						pet.SetControlMaster( from );

						pet.ControlTarget = from;
						pet.ControlOrder = OrderType.Follow;

						pet.MoveToWorld( from.Location, from.Map );

						pet.IsStabled = false;
						vendor.SayTo( from, 1042559 );	// Here you go... and good day to you!
					}
				}
				else if ( !from.Backpack.TryDropItem( from, item, false ) && !bank.TryDropItem( from, item, false ) )
				{
					from.SendLocalizedMessage( 1010008 );		// You didn't have enough room in either your bank box, or your backpack.
					from.AddToBackpack( item );		// drop it on the floor, if both backpack and bank is full
				}
				else
					from.SendMessage( "Item is in either your backpack, or your bank box." );

				RemoveTradeItem( from, vendor, item, pet );

				if ( item is CloneMobile )
				{
					item.Internalize();
					item.Delete();
				}
				else
					item.Movable = true;
			}
			catch
			{
				from.SendMessage( "Critical Error!" );
				return;
			}

		}

		// TODO: owner or staff may cancel the whole bid
		public static void ToCancelBid( Mobile from, TradeItem ti, BidItem bi )
		{
			string content = " To cancel bid:<br><br>"
				+ string.Format( " Item: {0}, Bid By: {1}", ti.Description, bi.m_bider.Name )
				+ "<br>" + string.Format( " Bid Amount: {0}", bi.m_bid )
				+ "<br><br> Do you wish to proceed?";

			from.SendGump( new WarningGump( 1060637, 30720, content, 0xFFC000, 420, 280, new WarningGumpCallback( CancelBid_Callback ), new object[] { ti, bi } ) );
		}

		public static void CancelBid_Callback( Mobile from, bool ok, object state )
		{
			if ( !ok )
				return;

			object[] aState = (object[]) state;

			TradeItem ti = (TradeItem) aState[0];
			BidItem bi = (BidItem) aState[1];

			Item item = (Item)ti.ItemObj;
			IHardsellable hs = item.RootParent as IHardsellable;

			for ( int i = 0; i < hs.BidItems.Count; i++ )
			{
				BidByItem bbi = hs.BidItems[i];

				if ( (Item)bbi.m_itemobj == item )
					hs.BidItems.RemoveAt( i-- );
			}
		}

		public static void ToApproveBid( Mobile from, TradeItem ti, BidItem bi )
		{
			string content = " To approve bid:<br><br>"
				+ string.Format( " Item: {0}, Bid By: {1}", ti.Description, bi.m_bider.Name )
				+ "<br>" + string.Format( " Bid Amount: {0}", bi.m_bid )
				+ "<br><br> Do you wish to proceed?";

			from.SendGump( new WarningGump( 1060637, 30720, content, 0xFFC000, 420, 280, new WarningGumpCallback( ConfirmBid_Callback ), new object[] { ti, bi } ) );
		}

		public static void ConfirmBid_Callback( Mobile from, bool ok, object state )
		{
			if ( !ok )
				return;

			object[] aState = (object[]) state;

			TradeItem ti = (TradeItem) aState[0];
			BidItem bi = (BidItem) aState[1];

			Item item = (Item)ti.ItemObj;
			IHardsellable hs = item.RootParent as IHardsellable;
			Mobile vendor = item.RootParent as Mobile;

			Mobile bider = bi.m_bider;
			BaseCreature pet = null;
			bool lok = true;

			if ( hs.HoldBox == null || hs.ShopItems == null || !( hs.ShopItems.Count > 0 ) || vendor is RestockableVendor )
				return;

			if ( !( hs.Owner == from || IsMaster( from, ti ) || from.AccessLevel >= AccessLevel.GameMaster ) )
			{
				from.SendLocalizedMessage( 1061637 );		// You are not allowed to access this.
				return;
			}

			Type gold = hs.Currency;
			int totalGold = 0;

			if ( bider.Backpack != null )
				totalGold += bider.Backpack.GetAmount( gold );

			if ( gold == typeof( Gold ) )
				totalGold += Banker.GetBalance( bider );
			else
				totalGold += GetCurrencyBalance( bider, gold );

			if ( totalGold < bi.m_bid )
			{
				from.SendMessage( "Bider has not enough gold." );
				return;
			}

			Container bank = bider.FindBankNoCreate();

			if ( bider.Backpack == null || bank == null )
			{
				from.SendMessage( "Error: bider's backpack or bank." );
				return;
			}

			try
			{
				if ( item is CloneMobile )
				{
					pet = ((CloneMobile)item).ClonePet as BaseCreature;
	
					if ( pet == null || pet.Deleted )
					{
						from.SendMessage( "Pet lost!" );
						lok = false;
					}
					else
					{
						int maxstable = AnimalTrainer.GetMaxStabled( bider );
						int stabled = bider.Stabled.Count;
	
						for ( int i = 0; i < SaveStone.TradePets.Count; i++ )
						{
							TradeMobile tm = SaveStone.TradePets[i];
	
							if ( tm.m_owner == bider )
								stabled++;
						}
	
						if ( from.AccessLevel < AccessLevel.GameMaster && stabled >= maxstable )
						{
							from.SendMessage( "Bider has too many pets on sale or in stables." );
							lok = false;
						}
					}
				}
				else if ( bider.Backpack.TryDropItem( bider, item, false ) )
					from.SendMessage( "You placed the item to the bider's backpack." );
				else if ( bank.TryDropItem( from, item, false ) )
					from.SendMessage( "You placed the item to the bider's bank box." );
				else
				{
					from.SendMessage( "Bider has no room in bank box and in backpack to hold the item." );
					lok = false;
				}

				if ( lok )
				{
					int leftPrice = bi.m_bid;

					if ( bider.Backpack != null )
						leftPrice -= bider.Backpack.ConsumeUpTo( gold, leftPrice );

					if ( leftPrice > 0 )
					{
						if ( gold == typeof( Gold ) )
							Banker.Withdraw( from, leftPrice );
						else
							leftPrice = bank.ConsumeUpTo( gold, leftPrice );
					}

					RemoveTradeItem( from, vendor, item, pet );

					int commission = 0;

						// will be billed by timer
					if ( hs.IsComm && Math.Abs( hs.CommRate ) <= 100 )
					{
						commission = (int)( bi.m_bid * Math.Abs( hs.CommRate ) / 100 );
					}

					int after = bi.m_bid - commission;

					if ( vendor is PetSeller && item is CloneMobile )
					{
						Mobile master = ((CloneMobile)item).Master;
						Type currency = hs.Currency;

						if ( from == bider && IsMaster( from, ti ) )
						{
							Deposit( master, bi.m_bid, currency );
							from.SendMessage( "You are the owner of this pet." );
						}
						else
						 Deposit( master, after, currency );
					}
					else
						hs.HoldGold += bi.m_bid;		// commission will be billed by timer

					// to update bid record
					List<BidRecord> list = new List<BidRecord>();
		
					if ( SaveStone.PlayerBids != null && SaveStone.PlayerBids.ContainsKey( bider ) )
						list = (List<BidRecord>)SaveStone.PlayerBids[bider];

					for ( int i = 0; i < list.Count; i++ )
					{
						BidRecord br = list[i];

						if ( br.m_serial == item.Serial )
							br.m_comment = "Succeeded";
					}

					if ( item is CloneMobile )
					{
						item.Internalize();
						item.Delete();

						if ( bider != from || !IsMaster( from, ti ) )
							pet.IsBonded = false;

						pet.ControlTarget = null;
						pet.ControlOrder = OrderType.Stay;
						pet.Internalize();

						pet.SetControlMaster( null );
						pet.SummonMaster = null;

						pet.IsStabled = true;
						pet.Blessed = false;

						if ( Core.SE )	
							pet.Loyalty = BaseCreature.MaxLoyalty; // Wonderfully happy

						if ( bider != from || !IsMaster( from, ti ) )
							pet.IsBonded = false;

						bider.Stabled.Add( pet );
					}
					else
						item.Movable = true;
				}
			}
			catch
			{
				from.SendMessage( "System error: bid approval error." );
				return;
			}
		}

		public static void CurrencyBuyItem( Mobile from, TradeItem ti )
		{
			Item item = (Item)ti.ItemObj;
			IHardsellable hs = item.RootParent as IHardsellable;
			Mobile vendor = item.RootParent as Mobile;

			BaseCreature pet = null;
			Item clone = null;
			bool ok = true;

			if ( hs.HoldBox == null || hs.ShopItems == null || !( hs.ShopItems.Count > 0 ) )
			{
				from.SendMessage( "Error: Shop error!." );
				return;
			}
			else if ( from.Map != vendor.Map || !from.InRange( vendor, 14 ) || !from.CheckAlive() )
			{
				vendor.SayTo( from, "I can't see you, sorry." );
				return;
			}

			if ( ti.Price < 0 )
			{
				from.SendLocalizedMessage( 1043307 ); 	// Price: Not for sale.
				return;
			}

			if ( vendor is RestockableVendor )
			{
				RestockableVendor res = vendor as RestockableVendor;

				int qty = VendorManager.GetBuyItemQuantity( res, ti, false );

				if ( qty <= 0 )
				{
					from.SendMessage( "Item out of stock, please come back later." );
					return;
				}

				try
				{
					if ( item is CloneMobile )
					{
						pet = DupeRestockablePet( from, ((CloneMobile)item).ClonePet as BaseCreature );

						if ( pet != null && pet is BaseCreature )
						{
							if ( ( from.Followers + pet.ControlSlots ) > from.FollowersMax )
							{
								vendor.SayTo( from, 1049607 );	// You have too many followers to control that creature.
								ok = false;
							}
							else
							{
								pet.Internalize();
							}
						}
						else
						{
							from.SendMessage( "Shop error, pet deal cancelled!" );
							return;
						}
					}
					else
					{
						clone = DupeRestockableItem( from, (Item)ti.ItemObj );

						if ( clone != null && clone is Item )
						{
							clone.Internalize();
							clone.InvalidateProperties();
							item = clone;
						}
						else
						{
							from.SendMessage( "Shop error, item deal cancelled!" );
							return;
						}
					}
				}
				catch
				{
					from.SendMessage( "Error, sales item cannot be created." );
					return;
				}
			}

			if ( !ok )
				return;

			Type gold = hs.Currency;
			int totalGold = 0;

			if ( from.Backpack != null )
				totalGold += from.Backpack.GetAmount( gold );

			if ( gold == typeof( Gold ) )
				totalGold += Banker.GetBalance( from );
			else
				totalGold += GetCurrencyBalance( from, gold );

			if ( totalGold < ti.Price )
			{
				vendor.SayTo( from, 503205 ); // You cannot afford this item.
				return;
			}

			Container bank = from.FindBankNoCreate();

			if ( from.Backpack == null || bank == null )
			{
				from.SendMessage( "Error: backpack or bank." );
				return;
			}

			try
			{
				if ( item is CloneMobile )
				{
					if ( !( vendor is RestockableVendor ) )
						pet = ((CloneMobile)item).ClonePet as BaseCreature;

					if ( pet == null || pet.Deleted )
					{
						from.SendMessage( "Pet lost!" );
						ok = false;
					}
					else
					{
						if ( ( from.Followers + pet.ControlSlots ) > from.FollowersMax )
						{
							vendor.SayTo( from, 1049607 );	// You have too many followers to control that creature.
							ok = false;
						}
					}
				}
				else if ( from.Backpack.TryDropItem( from, item, false ) )
					from.SendLocalizedMessage( 1077183 ); 	// You carefully place it in your backpack.
				else if ( bank.TryDropItem( from, item, false ) )
					from.SendLocalizedMessage( 1054150 );		// The item was placed in your bank box.
				else
				{
					ok = false;
					from.SendLocalizedMessage( 1010008 );		// You didn't have enough room in either your bank box, or your backpack.
				}

				if ( ok )
				{
					int leftPrice = ti.Price;

					if ( from.Backpack != null )
						leftPrice -= from.Backpack.ConsumeUpTo( gold, leftPrice );

					if ( leftPrice > 0 )
					{
						if ( gold == typeof( Gold ) )
							Banker.Withdraw( from, leftPrice );
						else
							leftPrice = bank.ConsumeUpTo( gold, leftPrice );
					}

					if ( !( vendor is RestockableVendor ) )
						RemoveTradeItem( from, vendor, item, pet );

					int commission = 0;

					if ( hs.IsComm && Math.Abs( hs.CommRate ) <= 100 )
					{
						commission = (int)( ti.Price * Math.Abs( hs.CommRate ) / 100 );
					}

					int after = ti.Price - commission;

					if ( vendor is PetSeller && item is CloneMobile )
					{
						Mobile master = ((CloneMobile)item).Master;
						Type currency = hs.Currency;

//						from.SendMessage( "Price: {0}, Comm: {1}, After: {2}.", ti.Price, commission, after );		// debug code

						if ( IsMaster( from, ti ) )
						{
							Deposit( master, ti.Price, currency );
							from.SendMessage( "You are the owner of this pet." );
						}
						else
						 Deposit( master, after, currency );
					}

					hs.HoldGold += ti.Price;		// commission will be billed by timer

					if ( item is CloneMobile )
					{
						if ( !( vendor is RestockableVendor ) )
						{
							item.Internalize();
							item.Delete();
						}

						if ( !IsMaster( from, ti ) )
							pet.IsBonded = false;

						pet.SetControlMaster( from );
						pet.Blessed = false;

						pet.ControlTarget = from;
						pet.ControlOrder = OrderType.Follow;

						pet.MoveToWorld( from.Location, from.Map );

						pet.IsStabled = false;
						vendor.SayTo( from, 1042559 );	// Here you go... and good day to you!
					}
					else
						item.Movable = true;
				}
				else
				{
					if ( vendor is RestockableVendor )
					{
						if ( pet != null )
							pet.Delete();
						else if ( item != null )
							item.Delete();
					}
				}
			}
			catch
			{
				from.SendMessage( "Critical error in buying." );

				if ( vendor is RestockableVendor )
				{
					if ( pet != null )
						pet.Delete();
					else if ( item != null )
						item.Delete();
				}

				return;
			}

			// to update restockable vendor items
			if ( ok && vendor is RestockableVendor )
			{
				RestockableVendor rv = vendor as RestockableVendor;

				GetBuyItemQuantity( rv, ti, true );		// to substract vendor's buyitem quantity
			}
		}

		public static int GetBuyItemQuantity( RestockableVendor rv, TradeItem ti, bool isUpdate )
		{
			Item item = (Item)ti.ItemObj;
			Type type = item.GetType();
			int qty = -1;

			IBuyItemInfo[] bi = rv.GetBuyInfo();
			int itemID = item.ItemID;

			for ( int i = 0; i < bi.Length; i++ )
			{
				if ( item is CloneMobile )
				{
					BaseCreature bc = ((CloneMobile)item).ClonePet as BaseCreature;
					type = bc.GetType();
					itemID = bc.Body;
				}

				GenericBuyInfo gbi = (GenericBuyInfo)bi[i];

				if ( gbi.Type == type && gbi.Hue == item.Hue )
				{
					qty = gbi.Amount;
					int amount = gbi.Amount;

					if ( amount > 0 && isUpdate )
						gbi.Amount--;
					else if ( amount < 0 )
						qty = 0;
				}
			}

			return qty;
		}

		public static bool Deposit( Mobile from, int amount, Type currency )
		{
			if ( currency == typeof( Gold ) )
				return Banker.Deposit( from, amount );

			BankBox box = from.FindBankNoCreate();
			if ( box == null )
				return false;

			List<Item> items = new List<Item>();

			while ( amount > 0 )
			{
				Item item;
				if ( amount <= 60000 )
				{
					item = (Item)Activator.CreateInstance( currency );
					item.Amount = amount;
					amount = 0;
				}
				else
				{
					item = (Item)Activator.CreateInstance( currency );
					item.Amount = 60000;
					amount -= 60000;
				}

				if ( box.TryDropItem( from, item, false ) )
				{
					items.Add( item );
				}
				else
				{
					item.Delete();
					foreach ( Item curItem in items )
					{
						curItem.Delete();
					}

					return false;
				}
			}

			return true;
		}

		public static int GetCurrencyBalance( Mobile from, Type currency )
		{
			Item[] gold;

			return GetCurrencyBalance( from, currency, out gold );
		}

		public static int GetCurrencyBalance( Mobile from, Type currency, out Item[] gold )
		{
			int balance = 0;

			Container bank = from.FindBankNoCreate();

			if ( bank != null )
			{
				gold = bank.FindItemsByType( currency );

				for ( int i = 0; i < gold.Length; ++i )
					balance += gold[i].Amount;
			}
			else
			{
				gold = new Item[0];
			}

			return balance;
		}

		public static bool CanTradeWith( Mobile from, Mobile vendor )
		{
			if ( !from.CanSee( vendor ) || !Utility.InUpdateRange( from, vendor ) || !from.CheckAlive() )
				return false;

			return true;
		}

		public static void SetLastBid( Mobile owner, BidByItem bbi )
		{
			if ( SaveStone.LastBids == null )
				SaveStone.LastBids = new Hashtable();

			SaveStone.LastBids[owner] = bbi;
		}

		public static TradeItem GetTradeItem( IHardsellable hs, Item item )
		{
			return (TradeItem) hs.ShopItems[item];
		}

		public static TradeItem SetTradeItem( IHardsellable hs, Item item, TradeItem ti )
		{
			hs.ShopItems[item] = ti;

			item.InvalidateProperties();
			return ti;
		}

		public static TradeItem SetTradeItem( IHardsellable hs, Item item, int price, string description )
		{
			RemoveTradeItem( item.RootParent as Mobile, item );

			TradeItem ti = new TradeItem( (object)item, price, description );
			hs.ShopItems[item] = ti;

			item.InvalidateProperties();
			return ti;
		}

		public static void RemoveTradeItem( Mobile from, Mobile vendor, Item item, BaseCreature pet )
		{
			if ( vendor == null || vendor.Deleted )
				return;

			IHardsellable hs = vendor as IHardsellable;

			if ( item is CloneMobile && pet != null && pet == (BaseCreature)((CloneMobile)item).ClonePet )
			{
				for ( int i = 0; i < SaveStone.TradePets.Count; i++ )
				{
					TradeMobile tm = SaveStone.TradePets[i];

					if ( tm.m_pet == pet )
						SaveStone.TradePets.RemoveAt( i-- );
				}
			}

			// to remove player bids
			for ( int i = 0; i < hs.BidItems.Count; i++ )
			{
				BidByItem bbi = hs.BidItems[i];

				if ( (Item)bbi.m_itemobj == item )
					hs.BidItems.RemoveAt( i-- );
			}

			RemoveTradeItem( from, vendor, item );
		}

		public static void RemoveTradeItem( Mobile vendor, Item item )
		{
			RemoveTradeItem( null, vendor, item );
		}

		public static void RemoveTradeItem( Mobile from, Mobile vendor, Item item )
		{
			if ( vendor == null || vendor.Deleted )
				return;

			IHardsellable hs = vendor as IHardsellable;
			TradeItem ti = GetTradeItem( hs, item );

			if ( ti != null )
			{
				if ( vendor is RestockableVendor && from != null )
				{
					ti.Price = -1;
					RemoveRestockableItem( from, vendor as RestockableVendor, item );
				}

				hs.ShopItems.Remove( item );

				item.InvalidateProperties();
			}
		}

		public static void RemoveRestockableItem( Mobile from, RestockableVendor rv, Item item )
		{
			Type type = item.GetType();

			if ( item is CloneMobile )
			{
				BaseCreature bc = ((CloneMobile)item).ClonePet as BaseCreature;
				type = bc.GetType();
			}

			if ( rv.ExternalBuyInfo != null && rv.ExternalBuyInfo.Count > 0 )
			{
				for ( int i = 0; i < rv.ExternalBuyInfo.Count; i++ )
				{
					GenericBuyInfo gbi = (GenericBuyInfo)rv.ExternalBuyInfo[i];

					if ( gbi.Type == type && gbi.Hue == item.Hue )
						rv.ExternalBuyInfo.RemoveAt( i-- );
				}
			}

			rv.Restock();
		}

		public static void AddVendorTile( Mobile from, Mobile manager )
		{
			from.SendMessage( "Select position to add tile." );
			from.Target = new AddTileTarget( manager );
		}

		// whether to display the buy and sell context menu
		public static bool CheckMenuAccess( BaseVendor vendor )
		{
			// Restockable vendor is also a wandering trader
			if ( vendor is WanderingTrader && !( vendor is RestockableVendor ) )
				return false;

			return true;
		}

		public static void AddHardItem( Mobile from, Mobile vendor )
		{
			from.SendMessage( "Select the item to be sold." );
			from.Target = new AddVendorItemTarget( from, vendor );
		}

		public static void SellHardItem( Mobile from, Mobile vendor )
		{
			if ( vendor == null || vendor.Deleted || !( vendor is IHardsellable ) )
				return;

			IHardsellable hs = vendor as IHardsellable;

			if ( hs.HoldBox == null || hs.ShopItems == null || !( hs.ShopItems.Count > 0 ) )
			{
				from.SendMessage( "Vendor has no item for sale." );
				return;
			}

			ArrayList buttons = HardItemDisplayGump.CreateVendorSalesItems( from, (Mobile)vendor, hs );

			if( buttons.Count > 0 && !from.HasGump( typeof( HardItemDisplayGump ) ) )
				from.SendGump( new HardItemDisplayGump( from, vendor, buttons ) );
		}

		public static void ListBidItems( Mobile m, Mobile vendor )
		{
			if ( vendor == null || vendor.Deleted || !( vendor is IHardsellable ) )
				return;

			IHardsellable hs = vendor as IHardsellable;

			if ( hs.BidItems == null || !( hs.BidItems.Count > 0 ) )
			{
				m.SendMessage( "Vendor receives no bid yet." );
				return;
			}

			ArrayList buttons = BidItemDisplayGump.CreateVendorBidItems( m, vendor );

			if( buttons.Count > 0 && !m.HasGump( typeof( BidItemDisplayGump ) ) )
				m.SendGump( new BidItemDisplayGump( m, vendor, buttons ) );
		}

		public static void UpdateRestockable( Mobile from, TradeItem ti, int amount )
		{
			Item item = ti.ItemObj as Item;
			Type type = item.GetType();
			int itemID = item.ItemID;

			if ( (Item)ti.ItemObj is CloneMobile )
			{
				BaseCreature bc = ((CloneMobile)ti.ItemObj).ClonePet as BaseCreature;
				type = bc.GetType();
				itemID = bc.Body;
			}

			RestockableVendor rv = item.RootParent as RestockableVendor;
			GenericBuyInfo binfo;

			int amt = 20;
			if ( amount > 0 )
				amt = amount;

			if ( rv == null || rv.Deleted )
			{
				from.SendMessage( "Vendor doesn't exist." );
				return;
			}

			if ( rv.ExternalBuyInfo != null && rv.ExternalBuyInfo.Count > 0 )
			{
				for ( int i = 0; i < rv.ExternalBuyInfo.Count; i++ )
				{
					GenericBuyInfo gbi = (GenericBuyInfo)rv.ExternalBuyInfo[i];

					if ( gbi.Type == type && gbi.Hue == item.Hue )
					{
						binfo = gbi;
						rv.ExternalBuyInfo.RemoveAt( i-- );

						if ( !( amount > 0 ) )
							amt = binfo.MaxAmount;
					}
				}
			}

			GenericBuyInfo bi = new GenericBuyInfo( ti.Description, type, ti.Price, amt, itemID, item.Hue );

			if ( bi != null )
			{
				rv.ExternalBuyInfo.Add( bi );
				rv.Restock();
				rv.SayTo( from, "Sales item updated." );
			}
			else
			{
				from.SendMessage( "Restockable sales item cannot be created." );
				return;
			}
		}

		public static BaseCreature DupeRestockablePet( Mobile from, BaseCreature pet )
		{
			Type t = pet.GetType();
			ConstructorInfo c = t.GetConstructor( Type.EmptyTypes );

			if ( c != null )
			{
				try
				{
					object o = c.Invoke( null );
		
					if ( o != null && o is BaseCreature )
					{
						BaseCreature clone = (BaseCreature)o;
	
						PropertyInfo[] props = pet.GetType().GetProperties();
			
						for ( int i = 0; i < props.Length; i++ )
						{
							try
							{
								if ( props[i].CanRead && props[i].CanWrite )
								{
									//Console.WriteLine( "Setting {0} = {1}", props[i].Name, props[i].GetValue( src, null ) );
									props[i].SetValue( clone, props[i].GetValue( pet, null ), null );
								}
							}
							catch
							{
								//Console.WriteLine( "Denied" );
							}
						}
	
						clone.Internalize();
	
						return clone;
					}
					else
					{
						from.SendMessage( "I can't handle that kind of pets." );
						return null;
					}
				}
				catch
				{
					from.SendMessage( "Error: can't create pet." );
					return null;
				}
			}
			else
			{
				from.SendMessage( "I don't sell that kind of pets." );
				return null;
			}
		}

		public static Item DupeRestockableItem( Mobile from, Item item )
		{
			Type t = item.GetType();
			ConstructorInfo c = t.GetConstructor( Type.EmptyTypes );

			if ( c != null )
			{
				try
				{
					object o = c.Invoke( null );
		
					if ( o != null && o is Item )
					{
						Item clone = (Item)o;
	
						CopyProperties( clone, item );
						item.OnAfterDuped( clone );
	
						clone.Internalize();
						clone.InvalidateProperties();
	
						return clone;
					}
					else
					{
						from.SendMessage( "I can't handle that kind of items." );
						return null;
					}
				}
				catch
				{
					from.SendMessage( "Error: can't create item." );
					return null;
				}
			}
			else
			{
				from.SendMessage( "I don't sell that kind of items." );
				return null;
			}
		}

		public static void CopyProperties( Item dest, Item src )
		{
			PropertyInfo[] props = src.GetType().GetProperties();

			for ( int i = 0; i < props.Length; i++ )
			{
				try
				{
					if ( props[i].CanRead && props[i].CanWrite )
					{
						//Console.WriteLine( "Setting {0} = {1}", props[i].Name, props[i].GetValue( src, null ) );
						props[i].SetValue( dest, props[i].GetValue( src, null ), null );
					}
				}
				catch
				{
					//Console.WriteLine( "Denied" );
				}
			}
		}

		public static void PlaceItemIn( Container parent, int x, int y, Item item )
		{
			parent.AddItem( item );
			item.Location = new Point3D( x, y, 0 );
		}

		public override void Serialize( GenericWriter writer )
		{
			base.Serialize( writer );
			writer.Write( (int) 1 ); // version

			if ( m_VendorArea != null && m_VendorArea.Length > 0 )
			{
				writer.Write( (int) m_VendorArea.Length );
		
				for ( int i = 0; i < m_VendorArea.Length; i++ )
					writer.Write( (Rectangle3D) m_VendorArea[i] );
			}
			else
				writer.Write( (int) 0 );

			writer.Write( (Point3D) m_AreaHome );
			writer.Write( (int) m_AreaRange );
			writer.Write( (int) m_CommRate );
			writer.Write( (int) m_BidPeriod );
			writer.Write( (int) m_ExtraSlot );
			writer.Write( (int) m_OwnerSerial );
			writer.Write( (bool) m_AllowHousing );

			writer.Write( (bool) m_RestrictMark );
			writer.Write( (bool) m_RestrictTeleport );
			writer.Write( (bool) m_RestrictRecall );

			writer.Write( (bool) m_AllowBid );
			writer.Write( (bool) m_BondOnly );
			writer.Write( (string) m_StringName );
			writer.Write( (byte)m_Dir );

			writer.Write( (int) m_TileID );
			writer.Write( (int) m_TileHue );

			writer.Write( (DateTime) m_ShowTime );		// not necessary though
			writer.Write( (double) m_ShowSpan );
			writer.Write( (TimeSpan) m_ShowDelay );

			if ( m_Workers != null && m_Workers.Count > 0 )
			{
				writer.Write( (int) m_Workers.Count );

				for ( int i = 0; i < m_Workers.Count; i++ )
					writer.Write( (Mobile) m_Workers[i] );
			}
			else
				writer.Write( (int) 0 );
		}

		public override void Deserialize( GenericReader reader )
		{
			base.Deserialize( reader );
			int version = reader.ReadInt();

			List<Rectangle3D> areas = new List<Rectangle3D>();
			for ( int i = reader.ReadInt(); i > 0; i-- )
				areas.Add( reader.ReadRect3D() );

      m_VendorArea = areas.ToArray();

			m_AreaHome = reader.ReadPoint3D();
			m_AreaRange = reader.ReadInt();
			m_CommRate = reader.ReadInt();
			m_BidPeriod = reader.ReadInt();
			m_ExtraSlot = reader.ReadInt();
			m_OwnerSerial = reader.ReadInt();
			m_AllowHousing = reader.ReadBool();
			m_RestrictMark = reader.ReadBool();

			if ( version > 0 )
			{
				m_RestrictTeleport = reader.ReadBool();
				m_RestrictRecall = reader.ReadBool();
			}

			m_AllowBid = reader.ReadBool();
			m_BondOnly = reader.ReadBool();
			m_StringName = reader.ReadString();

      UpdateMallRegion();		// m_StringName will be the region name

			m_Dir = (Direction)reader.ReadByte();
			Direction = m_Dir;

			m_TileID = reader.ReadInt();
			m_TileHue = reader.ReadInt();

			m_ShowTime = reader.ReadDateTime();
			m_ShowSpan = reader.ReadDouble();
			m_ShowDelay = reader.ReadTimeSpan();

			m_ShowTime = DateTime.Now + m_ShowDelay;		//  + TimeSpan.FromMinutes( m_ShowSpan )

			m_Workers = new List<Mobile>();
			for ( int i = reader.ReadInt(); i > 0; i-- )
			{
				Mobile worker = reader.ReadMobile();

				if ( worker != null && !worker.Deleted )
					m_Workers.Add( worker );
			}
		}


		private class AddTileTarget : Target
		{
			private VendorManager m_Manager;

			public AddTileTarget( Mobile manager ) : base( 6, false, TargetFlags.None )
			{
				m_Manager = manager as VendorManager;
			}

			protected override void OnTarget( Mobile from, object targeted )
			{
				VendorManager vm = m_Manager;
				IPoint3D p = targeted as IPoint3D;
				Map map = vm.Map;

				if ( p != null && vm != null && !vm.Deleted && targeted is Mobile && (Mobile)targeted == from )
				{
					IPooledEnumerable eable = map.GetItemsInRange( from.Location, 0 );

					foreach ( Item item in eable )
					{
						if ( item is CommVendorTile )
						{
							from.SendMessage( "Tile already exist." );
							return;
						}
					}

					Item tile = new CommVendorTile();

					Server.Commands.Add.Invoke( from, new Point3D( p ), new Point3D( p ), new string[]{ tile.GetType().Name, vm.TileID.ToString(), vm.TileHue.ToString() } );

					from.Target = new AddTileTarget( (Mobile) vm );
				}
				else
					from.SendMessage( "Target yourself to add tite to your standing spot." );
			}

			protected override void OnTargetCancel( Mobile from, TargetCancelType cancelType )
			{
				if ( cancelType == TargetCancelType.Canceled )
				{
					from.CloseGump( typeof( ManagerSetupGump ) );
					from.SendGump( new ManagerSetupGump( m_Manager ) );
				}
			}
		}

		private class SetupCurrencyTarget : Target
		{
			private Mobile m_From;
			private Mobile m_Vendor;

			public SetupCurrencyTarget( Mobile from, Mobile vendor ) : base( 1, false, TargetFlags.None )
			{
				m_From = from;
				m_Vendor = vendor;
			}

			// TODO: should not allow currency change when TradeItem exists
			protected override void OnTarget( Mobile from, object targeted )
			{
				if ( m_From == null || m_Vendor == null || m_Vendor.Deleted || !( m_Vendor is IHardsellable ) )
					return;
				else if ( !( targeted is Item ) || m_From.AccessLevel < AccessLevel.GameMaster )
					return;

				Item item = targeted as Item;
				IHardsellable hs = m_Vendor as IHardsellable;


				if ( !( item.IsChildOf( from.Backpack ) || item.IsChildOf( from.BankBox ) ) )
				{
					from.SendMessage( "That must be in your bank box or backpack to be valid." );
					return;
				}
				else if ( item.IsVirtualItem || !item.Movable )
					return;
				else if ( !( item.Stackable ) )
				{
					from.SendMessage( "Only stackable items can be used as currency." );
					return;
				}
				else if ( item.GetType() == hs.Currency )
				{
					from.SendMessage( "The currency is already in use." );
					return;
				}

				Type type = item.GetType();

				int itemID = item.ItemID;
				int hue = item.Hue;
				bool dupeicon = true;

				if ( itemID == 0xEED || itemID == 0xEEE )
					itemID = 0xEEF;		// Gold coin
				else if ( itemID == 0xEF0 || itemID == 0xEF1 )
					itemID = 0xEF2;		// Silver coin
				else if ( itemID == 0xEEA || itemID == 0xEEB )
					itemID = 0xEEC;		// Copper coin

				if ( itemID == 0xEEF || itemID == 0xEF2 || itemID == 0xEEC )
					dupeicon = false;

				CurrencyInfo cinfo = new CurrencyInfo( itemID, hue, dupeicon );

				if ( type != null && cinfo != null )
				{
					hs.CInfo = cinfo;
					hs.Currency = type;

					from.SendMessage( "{0} is used as the vendor's currency.", type.Name );
				}
				else
					from.SendMessage( "Problem setting currency." );
			}
		}


		private class AddVendorItemTarget : Target
		{
			private Mobile m_From;
			private Mobile m_Vendor;

			public AddVendorItemTarget( Mobile from, Mobile vendor ) : base( 1, false, TargetFlags.None )
			{
				m_From = from;
				m_Vendor = vendor;
			}

			protected override void OnTarget( Mobile from, object targeted )
			{
				if ( m_From == null || m_Vendor == null || m_Vendor.Deleted )
					return;
				else if ( !( targeted is Item || targeted is Mobile ) )
					return;
				else if ( !( m_Vendor is IHardsellable ) )
					return;

				IHardsellable hs = m_Vendor as IHardsellable;

				if ( hs.HoldBox == null || hs.HoldBox.Deleted )
				{
					m_From.SendMessage( "Vendor's inventory box lost." );
					return;
				}

				int price = -1;
				string desc = "";

				if ( targeted is Item )
				{
					if ( m_Vendor is PetSeller )
					{
						m_Vendor.SayTo( from, "I sell only pets, sorry." );
						return;
					}
					else if ( !((Item)targeted).IsChildOf( from.Backpack ) && !((Item)targeted).IsChildOf( from.BankBox ) )
						return;
					else if ( ((Item)targeted).IsVirtualItem || !((Item)targeted).Movable )
						return;

					Item item = targeted as Item;

					if ( m_Vendor is RestockableVendor )
					{
						// to detect same item in inventory
 						if ( hs.ShopItems != null && hs.ShopItems.Count > 0 )
 						{
							foreach ( TradeItem trm in hs.ShopItems.Values )
							{
								Item detect = (Item)trm.ItemObj;
	
//								from.SendMessage( "Type: {0}|{1}, Hue: {2}|{3}.", detect.GetType().Name, item.GetType().Name, (int)detect.Hue, (int)item.Hue );	// debug code

								if ( detect.GetType() == item.GetType() && detect.Hue == item.Hue )
								{
									m_Vendor.SayTo( from, "That item is already in my invenroy." );
									return;
								}
							}
						}

						Item cloneitem = DupeRestockableItem( from, item );

						if ( cloneitem != null && cloneitem is Item )
						{
							item = cloneitem;

							if ( item.Stackable && item.Amount > 1 )
								item.Amount = 1;
						}
						else
							return;
					}

//					desc = VendorManager.ToGetName( item, false );
					if ( item.Name == null )
						desc = CommonTools.GetFriendlyClassName( item.GetType().Name );
					else
						desc = item.Name;

					TradeItem ti = new TradeItem( item as object, price, desc );

					try
					{
						hs.HoldBox.DropItem( item );
						item.Movable = false;

						SetTradeItem( hs, item, ti );
					}
					catch
					{
						from.SendMessage( "Critical item ERROR." );
						if ( m_Vendor is RestockableVendor && item != null )
							item.Delete();

						return;
					}

					m_From.CloseGump( typeof( AddTradeItemGump ) );
					m_From.SendGump( new AddTradeItemGump( m_Vendor, item ) );
				}
				else if ( targeted is BaseCreature )
				{
					BaseCreature pet = targeted as BaseCreature;

					if ( pet.Deleted || pet.IsDeadPet || pet.Summoned || pet.Allured || !pet.Controlled || !( pet.ControlMaster == from ) )
						return;

					if ( hs.BondOnly && hs.Manager != null && hs.Manager.BondOnly && !pet.IsBonded )
					{
						m_Vendor.SayTo( from, "We sell bonded pets only." );
						return;
					}
					else if ( ( pet is PackLlama || pet is PackHorse || pet is Beetle ) && ( pet.Backpack != null && pet.Backpack.Items.Count > 0 ) )
					{
						m_Vendor.SayTo( from, 1042563 ); // You need to unload your pet.
						return;
					}

					if ( m_Vendor is PetSeller && !((PetSeller)m_Vendor).IsQualified( pet ) )
					{
						m_Vendor.SayTo( from, "We don't sell weak pets, give me tougher ones" );
						return;
					}

					int maxstable = AnimalTrainer.GetMaxStabled( from );
					int stabled = from.Stabled.Count;

					if ( hs.Manager != null && hs.Manager.ExtraSlot > 0 )
						maxstable += hs.Manager.ExtraSlot;

					for ( int i = 0; i < SaveStone.TradePets.Count; i++ )
					{
						TradeMobile tm = SaveStone.TradePets[i];

						if ( tm.m_owner == from )
							stabled++;
					}

					if ( from.AccessLevel < AccessLevel.GameMaster && stabled >= maxstable )
					{
						m_Vendor.SayTo( from, "You have too many pets on sale or in stables." );
						return;
					}

					if ( m_Vendor is RestockableVendor )
					{
						// to detect same item in inventory
						foreach ( TradeItem ti in hs.ShopItems.Values )
						{
							if ( ti.ItemObj is CloneMobile )
							{
								BaseCreature detect = ((CloneMobile)ti.ItemObj).ClonePet as BaseCreature;
	
//								from.SendMessage( "Type: {0}|{1}, Hue: {2}|{3}.", detect.GetType().Name, pet.GetType().Name, (int)detect.Hue, (int)pet.Hue );	// debug code
	
								if ( detect.GetType() == pet.GetType() && detect.Hue == pet.Hue )
								{
									m_Vendor.SayTo( from, "That pet is already in my invenroy." );
									return;
								}
							}
						}

						BaseCreature clonepet = DupeRestockablePet( from, pet );

						if ( clonepet != null && clonepet is BaseCreature )
							pet = clonepet;
						else
							return;
					}

					Item clone = new CloneMobile( from, pet );

					if ( clone != null )
					{
//						from.SendMessage( "Pet: {0}, Stable: {1}", ToGetName( clone, false ), stabled );	//debug code

						try
						{
							clone.Internalize();
							hs.HoldBox.DropItem( clone );
							clone.Movable = false;

//							desc = VendorManager.ToGetName( clone, false );
							if ( clone.Name == null )
								desc = CommonTools.GetFriendlyClassName( clone.GetType().Name );
							else
								desc = clone.Name;

							TradeItem ti = new TradeItem( clone as object, price, desc );

							SetTradeItem( hs, clone, ti );

							// register to global list
							TradeMobile tmob = new TradeMobile( from, m_Vendor, pet );
							SaveStone.TradePets.Add( tmob );

							// stable pet
							pet.ControlTarget = null;
							pet.ControlOrder = OrderType.Stay;
							pet.Internalize();

							pet.SetControlMaster( null );
							pet.SummonMaster = null;

							pet.IsStabled = true;
							pet.Loyalty = BaseCreature.MaxLoyalty;	// Wonderfully happy
						}
						catch
						{
							from.SendMessage( "Critical pet ERROR." );

							if ( m_Vendor is RestockableVendor && pet != null )
								pet.Delete();

							return;
						}

						if ( hs.Manager != null && DateTime.Now >= hs.Manager.ShowTime )
							hs.Manager.ShowTime += hs.Manager.ShowDelay;
					}
					else
					{
						m_Vendor.SayTo( from, "I can't sell that kind of pets." );
						return;
					}

					m_From.CloseGump( typeof( AddTradeItemGump ) );
					m_From.SendGump( new AddTradeItemGump( m_Vendor, clone ) );
				}

			}
		}

		private class ManagerSetupEntry : ContextMenuEntry
		{
			private Mobile m_Mobile;
			private VendorManager m_Manager;

			public ManagerSetupEntry( Mobile from, VendorManager manager ) : base( 0097, 3 )
			{
				m_Mobile = from;
				m_Manager = manager;

				Enabled = ( m_Mobile.Alive && m_Mobile.AccessLevel >= AccessLevel.GameMaster && !m_Manager.Deleted );
			}

			public override void OnClick()
			{
				m_Mobile.CloseGump( typeof( ManagerSetupGump ) );
				m_Mobile.SendGump( new ManagerSetupGump( m_Manager ) );
			}
		}

		private class ManagerComeEntry : ContextMenuEntry
		{
			private Mobile m_Mobile;
			private VendorManager m_Manager;

			public ManagerComeEntry( Mobile from, VendorManager manager ) : base( 5134, 12 )
			{
				m_Mobile = from;
				m_Manager = manager;

				Enabled = ( m_Mobile.Alive && m_Mobile.AccessLevel >= AccessLevel.GameMaster && !m_Manager.Deleted );
			}

			public override void OnClick()
			{
				ManagerComeHere( m_Mobile, m_Manager );
			}
		}

		private class StateInfo
		{
			public Map m_Map;
			public Point3D m_Start, m_End;
			public VendorManager m_Manager;

			public StateInfo( Map map, Point3D start, Point3D end, VendorManager vm )
			{
				m_Map = map;
				m_Start = start;
				m_End = end;

				m_Manager = vm;
			}
		}

	}

}
